Basic4GL, Copyright (C) 2003 Tom Mulgrew
Language guide
31-May-2005
Tom Mulgrew
This document is aimed at experienced programmers and
describes the basic syntax of Basic4GL programs.
This document focusses on the language itself, and and as such
does not go into the individual functions and constants, or how
they are intended to be used.
Basic4GL is designed to combine a simple, safe and easy to
understand programming language based on traditional BASIC with
the OpenGL graphics library, so that programmers can experiment
and learn OpenGL and beginning programmers can learn about
programming in general.
The downside is that Basic4GL cannot compete with programs
compiled to native machine code (i.e. from a C++ compiler). But
this was never the intention.
Basic4GL compiles programs to byte code, which it runs on a
virtual machine. This makes Basic4GL a safe language to
experiment in as the virtual machine protects the programs from
writing to invalid addresses or jumping to uninitialised code,
and handles cleaning up resources such as OpenGL textures
automatically.
In addition, the Basic4GL virtual machine automatically handles
certain setup tasks such as creating an OpenGL capable window
(and initialising OpenGL state), handling windows messages,
buffering keyboard input.
Basic4GL programs do not need to initialise OpenGL windows,
link to libraries, include header files or declare function
prototypes.
This means you can cut through all the paperwork and get straight
to the code that does the actual work.
The following examples are complete Basic4GL programs.
Example 1, A "Hello world" program:
print "Hello world!"
Example 2, drawing a square in OpenGL:
glTranslatef (0, 0, -5)
glBegin (GL_QUADS)
glVertex2f ( 1, 1): glVertex2f (-1, 1): glVertex2f (-1,-1): glVertex2f ( 1,-1)
glEnd ()
SwapBuffers ()
Comments are designated with a single quote.
All text from the quote to the end of the line are ignored by the
compiler.
' Program starts here
dim a 'Declare a variable
a = 5 'Initialise to a value
print a 'Print it to screen
Is equivalent to:
dim a
a = 5
print a
Basic4GL is a case insensitive language. This applies to all keywords and variable names, and infact anything except the contents of string constants.
The following lines are all equivalent:
GLVERTEX2F (X, Y)
glVertex2f (x, y)
glvertex2f(X, Y)
The following lines are not equivalent:
print "HELLO WORLD"
print "Hello World"
print "hello world"
(because the "Hello world"s are quoted strings).
Instructions are separated by colons ":" or new-lines.
The following code sample:
dim a$: a$ = "Hello": print a$
Is equivalent to:
dim a$
a$ = "Hello"
print a$
Basic4GL supports only 3 basic data types (although they can be combined into structures which are described further on).
Variables are declared and allocated explicitly with the
"Dim" instruction.
Attempting to use a variable without declaring it with
"Dim" will result in a compiler error.
A naming convention is used to designate the type of each variable, as follows:
All variables must be declared with Dim before use.
The format is:
Dim variable [, variable [, ...]]
For example:
Dim a
Dim name$
Dim a, b, c
Dim xOffset#, yOffset#
Dim ages(20)
Dim a, b, c, name$, xOffset#, yOffset#, ages(20)
Dim is both a declaration to the compiler that the keyword is
to be treated as a variable, and an executed instruction.
Therefore the Dim instruction must appear before the variable is
used.
This program:
a = 5
Dim a
Results in a compiler error, because the compiler encounters
'a' in an expression before it is declared with "Dim".
This program:
goto Skip
Dim a
Skip:
a = 5
Compiles successfully but results in a run time error, as it attempts to write to 'a' before the "Dim" instruction has executed, and therefore no storage space has yet been allocated for it.
The correct example is (of course):
Dim a
a = 5
Compatibility with other BASICsBasic4GL also supports the syntax:
Where type can be one of:
Note: Basic4GL has only one floating point type which is a single precision float (ie a "single). The "double" keyword is still accepted for compatibility, but Basic4GL still allocates a single precision floating pt number. |
Storage space is allocated when the "Dim"
instruction has been executed.
In addition, Basic4GL automatically initialises the data as
follows:
Attempting to Dim the same variable twice results in a runtime
error.
There is currently no way to re-dim a variable. However, this may
be included in a future version of Basic4GL.
Basic4GL supports single and multi-dimensional arrays. These are "Dim"med by specifying the array variable name, followed by a number in round brackets. Basic4GL will allocate elements from indices 0, through to and including the value specified in the brackets.
Examples:
Dim a$(10)
Dim size#(12)
const MaxThings = 12
Dim ThingHeight# (MaxThings), ThingWidth#(MaxThings)dim count: count = 10
Dim array(count), bigArray (count * 10)
For arrays of more than one dimension, each dimension is specified in its own pair of brackets.
Examples:
Dim matrix#(3)(3)
matrix#(2)(3) = 1const width = 20, height = 15
Dim grid(width)(height)
Is mentioned, Basic4GL allocates elements from indices 0,
through to and including the value specified in the brackets.
For example:
Dim a(3)
Will allocate four integers, named a(0), a(1), a(2) and a(3), and set their values to 0.
Basic4GL arrays are sized at runtime. You can use any
(expression that can be cast to an integer) to specify the number
of elements.
However, keep in mind that Basic4GL will stop with a runtime
error if you attempt to allocate array:
Basic4GL arrays can be copied by specifying the array name
without any brackets or indices. The target array must be the
same size as the copied array, otherwise a runtime error will
result.
Examples:
Dim a$(4), b$(4)
...
b$ = a$ ' Copy entire array from a$ to b$
Likewise some functions accept arrays as parameters, or return them as results:
Dim matrix#(3)(3)
matrix# = MatrixTranslate (-.5, -.5, -2)
glLoadMatrixf (matrix#)
glBegin (GL_TRIANGLES)
glVertex2f (0, 0): glVertex2f (1, 0): glVertex2f (0, 1)
glEnd ()
SwapBuffers ()
If you specify just one dimension of a 2D array, the result is
a 1D array, which can be assigned to/from variables or passed to
to/functions like any other 1D array of the same type.
Example:
dim vectors# (12)(3), temp#(3)
temp# = vectors# (4)
Likewise, specifying N dimensions of a M dimension array results in a (M - N) dimension array.
Compatibility with other BASICsBasic4GL also supports the syntax:
For multidimension arrays. E.g. dim grid(20, 10) grid (3, 7) = 12 Is exactly equivalent to: dim grid(20)(10) grid (3)(7) = 12 |
Early designs of Basic4GL were intended to allocate variables
automatically the first time they were encountered.
However Basic4GL is case insensitive, and OpenGL uses long
constants for bitmasks and flags.
Therefore, mistyping (or miss-spelling) a constant in an OpenGL function call such as:
glClear (GL_DEPTH_BUFER_BIT) ' Missing an "F" in "BUFFER"
Would have resulted in a code that still compiles, but instead
of passing the value of GL_DEPTH_BUFFER_BIT into the function,
Basic4GL would have created a new variable called
GL_DEPTH_BUFER_BIT, initialised the value to 0, and then passed 0
into the function.
This type of error can be very confusing and frustrating,
especially when learning a library such as OpenGL.
Therefore variables must be explicitly declared with Dim.
You can convert a variable, or an expression value to a different type, simply by assigning it to a variable of that type, providing the conversion type is one of the ones below:
Certain expression operators such as +, -, *, / can also result in an automatic conversion of either the left or right operand to match the other, using the following rules:
To use a literal integer in a Basic4GL program, simply specify the integer value. Examples:
Dim a: a = 5
Dim a: a = -5
Likewise to use a literal real:
Dim a#: a# = 3.14159265
Basic4GL does not support any numeric formats other than decimal.
To use a literal string, simply encase the string in double quotes. For example:
Dim helloString$: helloString$ = "Hello world!"
Basic4GL does not support literal prefix notations, such as \n
for newline in C/C++.
You can however use the Chr$() function to achieve the same
effect, for example:
Dim a$: a$ = "Bob says " + Chr$(34) + "Hello!" + Chr$ (34)
Print a$
Will output:
Bob says "Hello"
Basic4GL also has a number of named constants, such as M_PI
and GL_CULL_FACE.
For a complete list, click "Help|Function and Constant
list..." and click the "Constants" tab.
Note: Two commonly used constants are "true" and "false", which evaluate to -1 and 0 respectively.
You can add constants using the "Const" instruction.
The format is:
Const name = value [, name = value [, ...]]
Where:
For example:
const Things = 20
const Max = 100, Min = 1
const StepCount = 360, StepSize# = 2 * m_pi / StepCount
const major = 3, minor = 7, version$ = major + "." + minor
Certain instructions require constant expressions, such as the
"const" instruction (described above), and the
"step" part of the "for..next" instruction.
These expressions must always evaluate to the same value and
Basic4GL must be able to calculate this value at the time the
program is compiled.
An expression must satisfy these criteria to be considered "constant" by Basic4GL:
Examples:
-12
22.4
m_pi
m_pi / 180
true and not false
"banana"
"banana " + "split"
"Pi = " + m_pi
Are all valid constant expressions
Expressions are not considered constant if they
contain variables or functions. This holds even for expressions
that (to a human) are obviously constant.
For example:
sqrt (2)
length (vec3 (1, 1, 1))
Are not valid constant expressions in Basic4GL, even though it is clear to us that they will always evaluate to the same value.
Structures are used to group related information together into
a single "data structure".
The format is as follows:
Struc strucname
dim field [, field [,...]]
[dim field [,[field [,...]]]
[...]EndStruc
Example:
struc SPlayer
dim pos#(1), vel#(1)
dim dir#, lives, score, deadCounter, inGame
dim leftKey, rightKey, thrustKey, shootKey
dim wasShootingendstruc
This defines a data storage format. You can now allocate variables of the new structure type by using a special format of the "Dim" instruction:
Dim strucname variablename
Examples:
Dim SPlayer player
const maxPlayers = 10
Dim SPlayer players (maxPlayers)
Each variable now stores all the information described in the structure. You can access these individual fields using the "." operator as follows:
structurename.fieldname
For example:
player.pos#(0) = 12.3
players (4).score = players (4).score + 10
i = 3
print players (i).lives
You can also assign variables of the same structure type to
one another. This will copy all the fields from one variable to
the other.
Example:
player (7) = player (6)
Compatibility with other BASICsBasic4GL also supports the syntax:
E.g. struc SpaceMartian dim name$ dim x#, y# dim health(4) endstruc Is equivalent to: type SpaceMartian name as string x, y as single health(4) as integer end type (Except that in the first example the field names now have $ and # post-fixes.) |
Structures can contain arrays. Unlike regular arrays, the size of an array in a structure must be fixed at compile time. This means that the array size must be either a numeric constant, or a named constant.
For example:
struc STest: dim a(10): endstruc
const size = 20
struc STest2: dim array$(size): endstruc
Will work.
However this example:
dim size: size = 20
struc STest2: dim array$(size): endstruc
Will cause a compile time error, because size is now a variable and is not fixed at compile time. (Even though it's obvious to a human that it will always be 20!)
Basic4GL has a pointer syntax which is vaguely similar to C++'s 'reference' type, but a lot more simplified.
Pointers are declared by prefixing a "&"
character before the variable name in the "Dim"
statement.
The syntax is then the same as "Dim"ming a regular
variable, except that array dimensions must be specified with
"()" (i.e with no number in the brackets).
So whereas:
Dim i, r#, a$, array#(10), SomeStructure s, matrix#(3)(3)
Declares and allocates:
Dim &pi, &pr#, &pa$, &parray#(), SomeStructure &ps, &pmatrix#()()
Declares:
Pointer variables are initially unset. Attempting to read or write to the data of an unset pointer results in a runtime error. To do anything useful you need to point them to a variable, otherwise known as "set"ting them.
Pointers are set using this syntax:
&pointer = &variable
Examples:
Dim a$, &ptr$
a$ = "Hello world"
&ptr$ = &a$
print ptr$
Dim array(10), &element, i
for i = 1 to 10: &element = &array(i): element = i: nextdim matrix#(3)(3), &basisVector#(), axis, i
matrix# = MatrixIdentity ()
print "Axis? (0-3): ": axis = Val (input$ ()) ' Enter 4 to crash!
&basisVector# = &matrix# (axis)
for i = 0 to 3: print basisVector# (i) + " ": next
Once a pointer is set, it can be accessed like any other variable, i.e read, assigned to, passed to functions e.t.c. The actual data read from or written to will be that of the variable that it is pointing to.
Dim a, b, &ptr
&ptr = &a
a = 5 ' a is 5, b is 0
b = ptr ' a is 5, b is 5
ptr = b + 1 ' a is 6, b is 5
print "a = " + a + ", b = " + b
You can "un-set" a pointer by assigning it the special constant null, as follows:
Dim val, &ptr
&ptr = &val ' Pointer now set
&ptr = null ' Pointer now un-set
You can also compare a pointer to null.
if &ptr = null then
...
endif
if &ptr <> null then
...
endif
You can mix structures, arrays and pointers mostly in
any way you wish.
There are a few limitations to keep in mind however:
You cannot allocate an array of pointers, as:
Dim &ptrs()
will allocate a pointer to an array.
If you really need an array of pointers you can use the following workaround:
struc SPtr: dim &ptr: endstruc
dim SPtr array (100)
Then you can set the pointers using:
&array (5).ptr = &var
(or similar.)
Basic4GL supports a very simple memory allocation scheme. Memory once allocated is permanent (until the program finishes). There is no concept of freeing a block of allocated memory! (Note: While this has some obvious limitations, it does prevent a large number pointer related bugs. Keep in mind that Basic4GL was never intended to be the next C++...)
Data is allocated as follows:
alloc pointername [, arraysize [, arraysize [...]]]
Where pointername is the name of a Basic4GL pointer variable DIMmed earlier.
Examples:
dim &ptri
alloc ptri ' Allocate an integerdim &ptrr#
alloc ptrr# ' Allocate a real numerdim &ptrs$
alloc ptrs$ ' Allocate a stringstruc SPlayer: dim x, y, z: endstruc
dim SPlayer &ptrplayer
alloc ptrplayer ' Allocate a player structure
Basic4GL allocates a variable of the type that pointername points to, and then points pointername to the new variable.
To allocate an array, add a comma, and list the dimension sizes separated by commas.
Examples:
dim &ptrarray () ' Array size is not specified here!
alloc ptrarray, 100 ' Specified here instead!dim &ptrMatrix#()()
alloc ptrMatrix, 3, 3
As with DIMming arrays, specifiying N as the array size will
actually create N+1 elements: 0 through to N inclusive.
Also the array size is calculated at runtime, and is subject to
the same rules as DIMming an array (size must be at least 0
e.t.c).
Basic4GL evaluates infix expressions with full operator precedence.
In most loosely to most tightly bound order:
Operator | Description | Example |
or | Bitwise or | a# < 0 or a# > 1000 |
and | Bitwise and | a# >= 0 and a# <= 1000 |
xor | Bitwise exclusive or | a = a xor 255 |
not | Bitwise not | not a# = 5 |
= | Test for equal = can also be used to compare pointers of the same type, or to compare pointers to null. |
a# = 5 |
<> | Test for not equal <> can also be used to compare pointers of the same type, or to compare pointers to null. |
a# <> 5 |
> | Test for greater than | a > 10 |
>= | Test for greater or equal | a# >= 0 |
< | Test for less than | a# < 9.5 |
<= | Test for less or equal | a <= 1000 |
+ | Add numeric values, or concatenate strings | |
- | Subtract | |
* | Multiply | |
/ | Divide | |
% | Remainder | |
- (with single operand) | Negate | a * -b |
Notes:
Operators with equal precedence are evaluated from left to right.
You can force Basic4GL to evaluate expressions in a different order by enclosing parts of them in round brackets. For example:
(5 + 10) / 5
Will add 5 to 10, then divide the result by 5 (giving 3), whereas:
5 + 10 / 5
Will divide first, then add, and the resulting value will be 7.
Operators generally operate on standard integer, real and to a lesser extent string types. However certain operators have been extended to work with 1D and 2D arrays of real numbers for vector and matrix functions. These are explained in the Programmer's Guide. Also the = and <> operators can also be used to compare pointers to each other, or to compare pointers to null.
An expression operand can be any of the following:
Basic4GL stores boolean values as integers, where 0 is false and anything non 0 is true.
The comparison operators <, <=, =, >=, >, and <> all evaluate to -1 if the comparison is true or 0 if it is false.
The "and" and "or" operators perform a bitwise "and" or "or" of the respective operands.
Effectively this means that "and" and "or" can be used in both boolean expressions and bit manipulation.
Boolean example:
If a < 0 or a > 10 Then Print "Out of range": Endif
Bitwise example:
glClear (GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT)
Basic4GL does not support lazy evaluation. Expressions are always evaluated in full, even if this is not strictly necessary for a particular boolean expression.
Jumps directly to a new position in the source code.
Format:
Goto labelName
Where "labelName" is a Basic4GL label declared as
the first identifier on a line, followed by a colon.
Basic4GL will jump straight to the offset of the
"labelName" label, and continue execution.
For example:
Loop:
Print "Hello "
Goto Loop
Creates an infinite loop, where "Hello" is printed again and again.
Calls a subroutine.
Format:
Gosub labelName
Where "labelName" is a Basic4GL label, declared exactly the same way as with the "Goto" instruction.
The subroutine should directly follow the
"labelName" label, and be terminated with a
"Return" instruction.
When "Return" executes, Basic4GL will jump to the
instruction immediately after the "Gosub"
instruction.
Example:
Dim name$: name$ = "Bob"
locate 10, 10: gosub Name
locate 20, 4: gosub Name
locate 3, 15: gosub Name
locate 30, 20: gosub Name
end
Name:
print name$
Return
To encounter a "Return" instruction, without a
corresponding "Gosub" is a runtime error.
A "Gosub" without a "Return" will not cause a
runtime error, but will waste stack space.
If too many "Gosub"s are without "Return"s
will eventually cause a "stack overflow" runtime error
Executes a block of code conditionally.
Format:
If expression Then
If block
Endif
Or:
If expression Then
If block
Else
Else block
Endif
Basic4GL evaluates "expression". It must evaluate to
an integer (usually the result of a boolean expression).
If the expression evalutes to true (non zero), then the "If
block" instructions are executed.
Otherwise the "Else block" instructions are executed if
present.
Example 1:
If lives < 1 then
Print "Game Over"
End
Endif
Example 2:
If score > highscore Then
Print "New high score!"
highscore = score
Else
Print "Better luck next time."
Endif
Basic4GL also supports the "Elseif" keyword, which
is equivalent to an "else" followed by an
"if", but removes the need for an extra
"endif" at the end of the "if" structure.
Thus:
if expression1 then
...
elseif expression2 then
...
endif
Is equivalent to:
if expression then
...
else
if expression2 then
...
endif
endif
Any number of "endif" sections can be placed after the initial "if". You cannot place an "endif" after the "else" section however.
Example 3:
dim a for a = 0 to 10 if a = 0 then printr "Zero" elseif a = 1 then printr "One" elseif a = 2 then printr "Two" elseif a = 3 then printr "Three" elseif a = 4 then printr "Four" elseif a = 5 then printr "Five" elseif a = 6 then printr "Six" elseif a = 7 then printr "Seven" elseif a = 8 then printr "Eight" elseif a = 9 then printr "Nine" elseif a = 10 then printr "Ten" else printr "???" endif next
Example 4:
dim score print "Enter score (0-100): " score = Val (Input$ ()) print "Your grade is: " if score < 20 then printr "F" elseif score < 30 then printr "E" elseif score < 50 then printr "D" elseif score < 70 then printr "C" elseif score < 90 then printr "B" else printr "A" endif
Compatibility with other BASICsBasic4GL also supports the syntax:
The "if" must follow immediately after the "end", otherwise it will be interpreted as an "end" program instruction. |
Executes a code block repeatedly while an expression is true.
Format:
While expression
Code block
Wend
This creates a conditional loop. Basic4GL evalutes
"expression", which again must evaluate to an integer
(and is usually a boolean expression).
If the expression evaluates to false (zero), then Basic4GL will
jump straight to the instruction following the "Wend",
and continue.
If the expression evaluates to true Basic4GL will execute the
code block, then re-evaluate the expression.
Basic4GL will continue executing the code block until the
expression evaluates to false.
Example:
While lives > 0
' Do gameplay
...
Wend
' Game over
...
Used to create loops with a loop counter variable.
Format:
For variable = begin-value To end-value
Code block
Next
Or:
For variable = begin-value To end-value step step-constant
Code block
Next
This creates a loop, where "variable" counts from "begin-value" to "end-value". Variable must be a numeric type (integer or real), and cannot be an array element or structure field. Step-constant must be a constant expression (integer or real). If no "step" is given the step-constant defaults to 1.
Basic4GL will count either upwards or downwards depending on
whether the step-constant is positive or negative.
If step-constant is positive, the for..next construct is
exactly equivalent to:
variable = begin-value
While variable <= end-value
Code block
variable = variable + step-constant
Wend
If step-constant is negative, it is equivalent to:
variable = begin-value
While variable >= end-value
Code block
variable = variable + step-constant
Wend
And if step-constant is zero, it is equivalent to:
variable = begin-value
While variable <> end-value
Code block
Wend
Example 1:
Dim index For index = 1 to 10 Printr "Index = " + index Next
Example 2:
Dim count: count = 10 Dim squared(count), index For index = 0 to count squared (index) = index * index Next
Example 3:
dim angle# glTranslatef (0, 0, -3) glBegin (GL_LINE_LOOP) for angle# = 0 to 2 * m_pi step 2 * m_pi / 360 glVertex2f (sin (angle#), cos (angle#)) next glEnd () SwapBuffers ()
Example 4:
dim count for count = 10 to 1 step -1 cls: locate 20, 12: printr count Sleep (1000) next cls: locate 15, 12: print "Blast off!!"
Basic4GL provides the standard "Data", "Read" and "Reset" mechanism for entering data directly into programs. This is basically a shorthand way of hard-coding data into programs and is typically used to initialise arrays.
The actual data stored is a list of values. Each value is either a string or a number (int or real).
To specify the data elements, use "Data".
Format:
Data element [, element [, ...]]
Examples:
Dim 12.4, -3.4, 12, 0, 44
Dim My age, 20, My height, 156
Dim "A long time ago, in a galaxy far away, yada yada yada"
If the data element can be parsed as a number, it will be stored as such. Otherwise it will be stored as a string.
Strings can either be quoted (enclosed in double quotes) or
unquoted. Quoted strings can contain commas (,), colons (:) and
single quotes (').
Unquoted strings cannot contain these characters, because:
So it is best to quote strings if you are unsure.
In order to do something with the data, you need to read it into variables, using "Read".
Format:
Read variable [, variable [, ...]]
Variable must be a simple variable type, either a string, integer or real. (In otherwords you can't read a structure or an array with a single read statement, although you can write code to read each element individually).
Read copies an element of data into the variable, and then
moves the data position along one.
If there is no data, or the program has run out of data, you will
get an "Out of DATA" runtime error.
Attempting to read a string value into a number variable (integer
or real) will also generate a runtime error.
Example 1:
data age, 22, height, 175, shoesize, 12 dim name$(3), value(3), i for i = 1 to 3 read name$(i), value(i) next for i = 1 to 3 printr name$(i) + "=" + value(i) next
"Reset" tells Basic4GL where to start loading data from.
Format:
Reset labelname
Where labelname is a Basic4GL program label.
The next "Read" will begin reading data from the first
"Data" statement after labelname.
ThisData: data 1, 2, 3, 4, 5 ThatData: data cat, dog, fish, mouse, horse dim a$, i printr "1) This data" printr "2) That data" print "Please press 1 or 2" while a$ <> "1" and a$ <> "2" a$ = Inkey$ () wend if a$ = "1" then reset ThisData else reset ThatData endif printr for i = 1 to 5 read a$ printr a$ next
Basic4GL supports a number of external functions.
You can see a full list by selecting "Help|Function and
Constant list..." and selecting the "Functions"
tab.
This lists all the external functions Basic4GL recognises, along
with their return types (if they return a value), and parameter
types.
External functions are called with the following format:
FunctionName ([param [, param [, ...]])
Examples:
Beep()
glClear (GL_COLOR_BUFFER_BIT)
glVertex3f (-2.5, 10, 0)
A small number of functions do not require
their arguments to be enclosed in brackets (mainly for historical
reasons.)
These functions are: Cls, Print, Printr and Locate.
For example:
Cls
Locate 17, 12
Print "Hello"
Some functions return a value, which can be assigned to a
variable, used in an expression or as a parameter to another
external functionl.
Examples:
print Sqrt (2)
if ScanKeyDown (VK_UP) then ...
locate (TextCols()-Len(a$))/2, TextRows()/2: Print a$
There are two more Basic4GL instructions that have yet to be discussed.